--hotfix.lua

require 'fairygui.FairyGUI'
require 'fairygui.UnityEngine'

require("lssl")
require("lcodec")
require("lstdfs")
require("luabus")
require("ltimer")

local lmd5          = ssl.md5
local sformat       = string.format
local lclock_ms     = timer.clock_ms
local httpccodec    = codec.httpccodec
local co_yield      = coroutine.yield
local co_create     = coroutine.create
local co_resume     = coroutine.resume
local co_running    = coroutine.running

local SESSION_ID    = 1
local CONN_TIMEOUT  = 3000
local HTTP_TIMEOUT  = 5000
local HOTFIX_PATH   = os.getenv("USERPROFILE") .. "/AppData/LocalLow/Pathea Games/StoreEditor/hotfixs"

local co_yields     = {}
local hotfix_ok     = false
local hotfix_fail   = false
local httpcodec     = httpccodec()
local socket_mgr    = luabus.create_socket_mgr(64)
local proto_text    = luabus.eproto_type.text
local copy_options  = stdfs.copy_options

local function log_debug(fmt, ...)
    print(sformat("[hotfix][debug] " .. fmt, ...))
end 

local function http_yield(session_id, title, ms_to, ...)
    local context = {co = co_running(), title = title, to = lclock_ms() + ms_to}
    co_yields[session_id] = context
    return co_yield(...)
end

local function http_response(session_id, ...)
    local context = co_yields[session_id]
    if not context then
        return false
    end
    co_yields[session_id] = nil
    co_resume(context.co, ...)
end

local function build_session_id()
    SESSION_ID = SESSION_ID + 1
    return SESSION_ID
end

local function init_http_socket(ip, port, headers)
    local socket, cerr = socket_mgr.connect(ip, port, CONN_TIMEOUT, proto_text)
    if not socket then
        log_debug("failed to connect: %s:%s err=%s", ip, port, cerr)
        return false, cerr
    end
    headers["Host"] = "127.0.0.1"
    headers["User-Agent"] = "quanta"
    socket.set_codec(httpcodec)

    --设置阻塞id
    local block_id = build_session_id()
    socket.on_connect = function(res)
        log_debug("init_http_socket on_connect!: %s", res)
        http_response(block_id, res == "ok", res)
    end
    socket.on_call_data = function(recv_len, proto, status, headers, body)
        log_debug("socket recv: status=%s, body=%s", status, body)
        local session_id = socket.session_id
        http_response(session_id, true, status, body, headers)
        socket.close()
    end
    socket.on_error = function(stoken, err)
        log_debug("client(token:%s) close(%s)!", stoken, err)
        local session_id = socket.session_id
        if session_id then
            http_response(session_id, false, err)
        end
    end
    --阻塞挂起
    local ok, res = http_yield(block_id, "connect", CONN_TIMEOUT)
    if not ok then
        return nil, res
    end
    log_debug("init_http_socket success!")
    return socket
end

--构建请求
local function send_request(ip, port, url)
    local headers = {["Accept"] = "*/*" }
    local socket, err = init_http_socket(ip, port, headers)
    if not socket then
        return false, err
    end
    local session_id = build_session_id()
    socket.session_id = session_id
    socket.call_data(url, "GET", headers, "")
    return http_yield(session_id, url, HTTP_TIMEOUT)
end

local function hotfix_fork(f, ...)
    local co = co_create(f)
    co_resume(co)
end

local function hotfix_cleck(filename, md5)
    local file = io.open(filename, "r")
    if not file then
        return false
    end
    local data = file:read("*a")
    file:close()
    return lmd5(data) == md5
end

local function hotfix_download(filename)
    local ok, ststus, data_or_err = send_request("10.96.8.100", 8080, "/store_editor/" .. filename)
    if not ok or ststus ~= 200 then
        log_debug("hotfix_download send_request failed (%s: %s)!", ststus, data_or_err)
        return false
    end
    local nfname = stdfs.append(HOTFIX_PATH, filename)
    local sfpath = stdfs.parent_path(nfname)
    stdfs.mkdir(sfpath)
    local file = io.open(nfname, "wb")
    if not file then
        log_debug("hotfix_download open file(%s) failed!", nfname)
        return
    end
    --写文件
    file:write(data_or_err)
    file:close()
    return true
end

local function hotfix_fetch()
    local ok, ststus, data_or_err = send_request("10.96.8.100", 8080, "/store_editor/files.lua")
    if not ok then
        log_debug("hotfix_fetch send_request failed (%s: %s)!", ststus, data_or_err)
        hotfix_fail = true
        return
    end
    log_debug("hotfix_fetch files: %s: %s", data_or_err, type(data_or_err))
    local f = load(data_or_err)
    if not f then
        log_debug("hotfix_fetch not function(%s)!", data_or_err)
        return
    end
    local lok, files_or_err = pcall(load(data_or_err))
    if not lok then
        log_debug("hotfix_fetch load failed(%s)!", files_or_err)
        return
    end
    for filename, md5 in pairs(files_or_err) do
        if hotfix_cleck(filename, md5) then
            log_debug("hotfix_fetch file [%s] already exists!", filename)
            goto continue
        end
        local ok = hotfix_download(filename)
        if not ok then
            hotfix_fail = true
            return
        end
        :: continue ::
    end
    local ok, err = stdfs.copy(HOTFIX_PATH, "./Lua", copy_options.recursive | copy_options.overwrite_existing)
    if not ok then
        log_debug("hotfix_fetch copy failed(%s)!", err)
        return
    end
    hotfix_ok = true
end

function hotfix()
    --清理旧文件
    stdfs.remove(HOTFIX_PATH, true)
    --创建目录
    stdfs.mkdir(HOTFIX_PATH)
    --开始检测
    hotfix_fork(hotfix_fetch)
end

function Update()
    local sclock_ms = lclock_ms()
    socket_mgr.wait(sclock_ms, 10)
    if hotfix_fail then
        log_debug("hotfix failed!")
        require("main")
        return
    end
    if hotfix_ok then
        log_debug("hotfix success!")
        require("main")
    end
end

hotfix()
